package com.x.provider.customer.service.customer;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.x.core.utils.ApiAssetUtil;
import com.x.core.utils.CompareUtils;
import com.x.core.utils.DateUtils;
import com.x.core.utils.MyBatisPlusUtil;
import com.x.provider.api.customer.model.dto.customer.CustomerResourceOperReqDTO;
import com.x.provider.api.customer.enums.CustomerResourceOperBizScene;
import com.x.provider.customer.enums.CustomerErrorCode;
import com.x.provider.customer.mapper.CustomerResourceBillMapper;
import com.x.provider.customer.mapper.CustomerResourceMapper;
import com.x.provider.customer.model.domain.customer.CustomerResource;
import com.x.provider.customer.model.domain.customer.CustomerResourceBill;
import com.x.provider.customer.model.query.customer.CustomerResourceBillQuery;
import com.x.provider.customer.model.query.customer.CustomerResourceQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class CustomerResourceServiceImpl implements CustomerResourceService{

    private static final String UNIQUE_ID_TOTAL = "0";

    private final CustomerResourceMapper customerResourceMapper;
    private final CustomerResourceBillMapper billMapper;

    public CustomerResourceServiceImpl(CustomerResourceMapper customerResourceMapper,
                                       CustomerResourceBillMapper billMapper){
        this.customerResourceMapper = customerResourceMapper;
        this.billMapper = billMapper;
    }

    @Override
    public Map<Long, List<CustomerResource>> listCustomerResource(List<Long> customerIdList) {
        Map<Long, List<CustomerResource>> customerResourceMap = list(CustomerResourceQuery.builder().customerIdList(customerIdList).geEndDate(new Date()).geCount(1L).build()).stream()
                .collect(Collectors.groupingBy(CustomerResource::getCustomerId));
        return customerResourceMap;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long operateCustomerResource(CustomerResourceOperReqDTO reqDTO) {
        if (reqDTO.getCount() == null || reqDTO.getCount() > 0){
            return addResource(reqDTO);
        }
        return descResource(reqDTO);
    }

    private Long descResource(CustomerResourceOperReqDTO reqDTO){
        CustomerResourceOperBizScene.CustomerResourceDescBizSceneEnum bizScene = CustomerResourceOperBizScene.CustomerResourceDescBizSceneEnum.valueOf(reqDTO.getOperBizSceneId());
        List<CustomerResource> customerResourceList = list(CustomerResourceQuery.builder().resourceId(reqDTO.getResourceId()).customerId(reqDTO.getCustomerId()).gtEndDate(new Date()).build());
        customerResourceList = customerResourceList.stream().sorted(Comparator.comparing(CustomerResource::getEndDate)).collect(Collectors.toList());
        if (reqDTO.getCount() == null){
            reqDTO.setCount(bizScene.getCount());
        }
        if (reqDTO.getResourceId() == null){
            reqDTO.setResourceId(bizScene.getResourceId());
        }
        ApiAssetUtil.isTrue(customerResourceList.stream().mapToLong(CustomerResource::getCount).sum() > reqDTO.getCount(), CustomerErrorCode.CUSTOMER_RESOURCE_NOT_ENOUGH);
        if (reqDTO.getTryOper() != null && reqDTO.getTryOper()){
            return customerResourceList.stream().mapToLong(CustomerResource::getCount).sum();
        }
        List<CustomerResource> descResource = descCustomerResource(customerResourceList, reqDTO.getCount());
        descResource.stream().forEach(item -> {
            customerResourceMapper.updateById(item);
        });
        return customerResourceList.stream().mapToLong(CustomerResource::getCount).sum();
    }

    private List<CustomerResource> descCustomerResource(List<CustomerResource> resourceList, Long count){
        List<CustomerResource> result = new ArrayList<>();
        for (CustomerResource resource : resourceList){
            if (count >= 0){
                return result;
            }
            if (resource.getCount() <= 0){
                continue;
            }
            long desc = resource.getCount() + count >= 0 ? count : - resource.getCount();
            count -= desc;
            resource.setCount(resource.getCount() + desc);
            result.add(resource);
        }
        return result;
    }

    private Long addResource(CustomerResourceOperReqDTO reqDTO){
        CustomerResourceOperBizScene.CustomerResourceAddBizSceneEnum bizScene = CustomerResourceOperBizScene.CustomerResourceAddBizSceneEnum.valueOf(reqDTO.getOperBizSceneId());
        Date nowDate = new Date();
        Date startDate = getStartDate(bizScene, nowDate);
        Date endDate = getEndDate(bizScene, startDate);
        if (bizScene != null) {
            reqDTO.setResourceId(bizScene.getResourceId());
            reqDTO.setCount(bizScene.getCount());
        }
        List<CustomerResource> customerResourceList = list(CustomerResourceQuery.builder().resourceId(reqDTO.getResourceId()).customerId(reqDTO.getCustomerId()).gtEndDate(new Date()).build());
        CustomerResource customerResource = get(CustomerResourceQuery.builder().customerId(reqDTO.getCustomerId()).resourceId(reqDTO.getResourceId()).startDate(startDate)
                .endDate(endDate).period(bizScene.getPeriod()).build());
        if (reqDTO.getCount() == null){
            reqDTO.setCount(bizScene.getCount());
        }
        if (reqDTO.getResourceId() == null){
            reqDTO.setResourceId(bizScene.getResourceId());
        }
        if (CompareUtils.gtZero(bizScene.getMaxAddCount()) && customerResource != null){
            Integer countBill = countBill(CustomerResourceBillQuery.builder().bizSceneId(bizScene.getId()).customerResourceId(customerResource.getId()).geCount(1L).build());
            if (countBill >= bizScene.getMaxAddCount()){
                return customerResourceList.stream().mapToLong(CustomerResource::getCount).sum();
            }
        }
        if (customerResource == null){
            customerResource = CustomerResource.builder().startDate(startDate).endDate(endDate).customerId(reqDTO.getCustomerId())
                    .resourceId(reqDTO.getResourceId()).period(bizScene.getPeriod()).count(reqDTO.getCount()).build();
        }
        else{
            customerResource.setCount(customerResource.getCount() + reqDTO.getCount());
        }
        MyBatisPlusUtil.save(customerResourceMapper, customerResource);
        billMapper.insert(CustomerResourceBill.builder().bizSceneId(bizScene.getId()).customerResourceId(customerResource.getId()).count(reqDTO.getCount()).build());
        return customerResourceList.stream().mapToLong(CustomerResource::getCount).sum() + reqDTO.getCount();
    }

    private Date getStartDate(CustomerResourceOperBizScene.CustomerResourceAddBizSceneEnum bizScene, Date date){
        if (Objects.equals(bizScene.getPeriod(),Calendar.DAY_OF_YEAR)){
            return DateUtil.beginOfDay(date);
        }
        return DateUtils.minDate();
    }

    private Date getEndDate(CustomerResourceOperBizScene.CustomerResourceAddBizSceneEnum bizScene, Date date){
        if (Objects.equals(bizScene.getPeriod(),Calendar.DAY_OF_YEAR)) {
            return DateUtil.endOfDay(date);
        }
        return DateUtils.maxDate();
    }

    private List<CustomerResource> list(CustomerResourceQuery query){
        return customerResourceMapper.selectList(build(query));
    }

    private CustomerResource get(CustomerResourceQuery query){
        return customerResourceMapper.selectOne(build(query));
    }

    private Integer countBill(CustomerResourceBillQuery query){
        return billMapper.selectCount(buildBill(query)).intValue();
    }

    private LambdaQueryWrapper<CustomerResource> build(CustomerResourceQuery query){
        LambdaQueryWrapper<CustomerResource> queryWrapper = new LambdaQueryWrapper<>();
        if (query.getCustomerId() != null){
            queryWrapper.eq(CustomerResource::getCustomerId, query.getCustomerId());
        }
        if (!CollectionUtils.isEmpty(query.getCustomerIdList())){
            queryWrapper.in(CustomerResource::getCustomerId, query.getCustomerIdList());
        }
        if (query.getGtEndDate() != null){
            queryWrapper.gt(CustomerResource::getEndDate, query.getGtEndDate());
        }
        if (query.getStartDate() != null){
            queryWrapper.eq(CustomerResource::getStartDate, query.getStartDate());
        }
        if (query.getPeriod() != null){
            queryWrapper.eq(CustomerResource::getPeriod, query.getPeriod());
        }
        if (query.getGeEndDate() != null){
            queryWrapper.ge(CustomerResource::getEndDate, query.getGeEndDate());
        }
        if (query.getGeCount() != null){
            queryWrapper.ge(CustomerResource::getCount, query.getGeCount());
        }
        if (query.getResourceId() != null){
            queryWrapper.eq(CustomerResource::getResourceId, query.getResourceId());
        }
        return queryWrapper;
    }

    private LambdaQueryWrapper<CustomerResourceBill> buildBill(CustomerResourceBillQuery query){
        LambdaQueryWrapper<CustomerResourceBill> queryWrapper = new LambdaQueryWrapper<>();
        if (query.getBizSceneId() != null){
            queryWrapper.eq(CustomerResourceBill::getBizSceneId, query.getBizSceneId());
        }
        if (query.getGeCount() != null){
            queryWrapper.ge(CustomerResourceBill::getCount, query.getGeCount());
        }
        if (query.getCustomerResourceId() != null){
            queryWrapper.eq(CustomerResourceBill::getCustomerResourceId, query.getCustomerResourceId());
        }
        return queryWrapper;
    }
}
